﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace ArgusPCLib.Reflection
{
	public class DelegateWrapper<T> where T:class
	{
		static DelegateWrapper()
		{
			if (!typeof(T).Is(typeof(Delegate)))
				throw new NotSupportedException("T must be a delegate-type.");
		}

		T _delegate;

		public DelegateWrapper(T _delegate)
		{
			this._delegate = _delegate;
		}

		public static implicit operator T(DelegateWrapper<T> _delegate)
		{
			return _delegate._delegate;
		}

		public static implicit operator DelegateWrapper<T>(T _delegate)
		{
			return new DelegateWrapper<T>(_delegate);
		}

		public static implicit operator MethodInfo(DelegateWrapper<T> _delegate)
		{
			return ((Delegate)(object)_delegate._delegate).GetMethodInfo();
		}
	}

	public class MethodSignature
	{
		public string Name { get; private set; }
		public Type ReturnType { get; private set; }
		public Type[] ParameterTypes { get; private set; }
		public bool IsPublic { get; private set; }
		public bool IsStatic { get; private set; }
		public Type DeclaringType { get; private set; }

		public MethodSignature(string name, Type returnType, Type[] parameterTypes, bool isPublic, bool isStatic)
			: this(null, name, returnType, parameterTypes, isPublic, isStatic) { }

		public MethodSignature(Type declaringType, string name, Type returnType, Type[] parameterTypes, bool isPublic, bool isStatic)
		{
			this.DeclaringType = declaringType;
			this.Name = name;
			this.ReturnType = returnType;
			this.ParameterTypes = parameterTypes;
			this.IsPublic = isPublic;
			this.IsStatic = isStatic;
		}

		public MethodSignature(MethodInfo mi)
			:this(mi.DeclaringType, mi.Name, mi.ReturnType, null, mi.IsPublic, mi.IsStatic)
		{
			ParameterInfo[] pis = mi.GetParameters();
			this.ParameterTypes = new Type[pis.Length];
			for (int i = 0; i < pis.Length; i++)
				this.ParameterTypes[i] = pis[i].ParameterType;
		}

		public MethodSignature(Delegate d)
			: this(d.GetMethodInfo()) { }

		public override bool Equals(object obj)
		{
			MethodSignature ms = obj as MethodSignature;
			if (ms == null)
				return false;

			return (this == ms);
		}

		public static bool operator ==(MethodSignature a, MethodSignature b)
		{
			if (a.DeclaringType != null && b.DeclaringType != null && a.DeclaringType != b.DeclaringType)
				return false;
			if (a.Name != b.Name)
				return false;
			if (a.ReturnType != b.ReturnType)
				return false;
			if (a.IsPublic != b.IsPublic)
				return false;
			if (a.IsStatic != b.IsStatic)
				return false;
			if (a.ParameterTypes.Length != b.ParameterTypes.Length)
				return false;
			for (int i = 0; i < a.ParameterTypes.Length; i++)
			{
				if (a.ParameterTypes[i] != b.ParameterTypes[i])
					return false;
			}
			return true;
		}

		public static bool operator !=(MethodSignature a, MethodSignature b)
		{
			return !(a == b);
		}

		public MethodInfo GetMethodInfo(Type declaringType)
		{
			return declaringType.GetRuntimeMethod(this.Name, this.ParameterTypes, !this.IsPublic, this.IsStatic);
		}

		public T GetDelegate<T>(Type declaringType) where T : class
		{
			Type delegateType = typeof(T);
			if (!delegateType.Is(typeof(Delegate)))
				throw new NotSupportedException("T must be a delegate.");
			MethodInfo mi = this.GetMethodInfo(declaringType);
			if (mi == null)
				return null;
			try { return (T)(object)mi.CreateDelegate(delegateType); }
			catch { return null; }
		}

		public T GetDelegate<T>() where T : class
		{
			return this.GetDelegate<T>(this.DeclaringType);
		}

		public static implicit operator MethodSignature(Delegate d)
		{
			return new MethodSignature(d.GetMethodInfo());
		}

		public static implicit operator MethodInfo(MethodSignature ms)
		{
			if (ms.DeclaringType == null)
				return null;
			return ms.GetMethodInfo(ms.DeclaringType);
		}

		public static implicit operator MethodSignature(MethodInfo mi)
		{
			return new MethodSignature(mi);
		}
	}
}
